﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Shapes;

using System.Collections.Generic;


namespace ManPowerEngine
{
	public class GameObject
	{
		/// <summary>
		/// Name of this object
		/// </summary>
		public string Name;
		/// <summary>
		/// A customized tag attached to this object
		/// </summary>
		public Object Tag;

		/// <summary>
		/// Temporarily disable this node and its children
		/// </summary>
		public bool Enabled = true;

		private RendererBase mRenderer;

		private Rigidbody mRigidbody;

		private ColliderBase mCollider;

		private static GameObject msRoot = null;
		private static GameObject msOverlayRoot = null;
		/// <summary>
		/// assigned by its parent
		/// </summary>
		internal GameObject mParent;

		private Transform mTransform;
		private List<GameObject> mChildren;

		/// <summary>
		/// Override this to add custom update event
		/// </summary>
		/// <param name="Time">Time Arguments</param>
		protected virtual void Update(TimeArgs Time) { }

		/// <summary>
		/// Override this to add custom stop event
		/// </summary>
		protected virtual void Stop() { }

		/// <summary>
		/// Override this to add custom physics update event
		/// </summary>
		/// <param name="TimeSinceLastIteration">Time since last iteration</param>
		protected virtual void PhysicsUpdate(double TimeSinceLastIteration) { }

		/// <summary>
		/// Override this to add custom collide event
		/// </summary>
		/// <param name="collidewith">the collision parameters</param>
		protected virtual void OnCollide(Collision collision) { }

		/// <summary>
		/// Basic GameObject constructor.
		/// </summary>
		public GameObject()
		{
			// the first game object will become the root, the second game object will become the overlay root
			if (msRoot == null)
			{
				msRoot = this;
			}
			else if (msOverlayRoot == null)
			{
				msOverlayRoot = this;
			}
			mTransform = new Transform(this);
			mChildren = new List<GameObject>();
		}

		/// <summary>
		/// Create a GameObject with a name
		/// </summary>
		/// <param name="name">Name of this game object</param>
		public GameObject(string name)
			: this()
		{
			Name = name;
		}

		/// <summary>
		/// Create a GameObject with a name, a default location relative to its parent and a default local rotation.
		/// </summary>
		/// <param name="name">Name of this game object</param>
		/// <param name="localPosition">Location relative to its future parent</param>
		/// <param name="localRotation">rotation relative to its future parent</param>
		public GameObject(string name, Vector localPosition, double localRotation)
			: this(name)
		{
			Transform.LocalPosition = localPosition;
			Transform.LocalRotation = localRotation;
		}
		/// <summary>
		/// The root game object
		/// </summary>
		public static GameObject Root
		{
			get { return msRoot; }
		}

		/// <summary>
		/// The overlay root game object
		/// </summary>
		public static GameObject OverlayRoot
		{
			get { return msOverlayRoot; }
		}

		/// <summary>
		/// parent of this game object. Null will be returned if this is the root object
		/// </summary>
		public GameObject Parent
		{
			get { return mParent; }
		}

		/// <summary>
		/// Transform component attached to this object.
		/// </summary>
		public Transform Transform
		{
			get { return mTransform; }
		}
		/// <summary>
		/// Renderer component attached to this object. If there is no renderer attached, null will be returned.
		/// </summary>
		public RendererBase Renderer
		{
			get { return mRenderer; }
			set
			{
				value._SetGameObject(this);
				mRenderer = value;
			}
		}

		/// <summary>
		/// Rigidbody component attached to this object. If there is no rigidbody attached, null will be returned.
		/// </summary>
		public Rigidbody Rigidbody
		{
			get { return mRigidbody; }
			set
			{
				value._SetGameObject(this);
				mRigidbody = value;
			}
		}

		/// <summary>
		/// Collider component attached to this object. If there is no collider attached, null will be returned.
		/// </summary>
		public ColliderBase Collider
		{
			get { return mCollider; }
			set
			{
				value._SetGameObject(this);
				mCollider = value;
			}
		}

		/// <summary>
		/// Children list of this GameObject
		/// </summary>
		public List<GameObject> Children
		{
			get { return mChildren; }
		}

		/// <summary>
		/// Add a new child to the hierarchy. This method is preferred than Children.Add()
		/// </summary>
		/// <param name="newchild">The new game object that is going to be added as a child</param>
		public void AddChild(GameObject newchild)
		{
			if (newchild.mParent != null)
			{
				throw new ManPowerException("You can't add a game object that already has a parent as a child: " + newchild.ToString());
			}
			newchild.mParent = this;
			Children.Add(newchild);
		}

		/// <summary>
		/// Remove an existing child.
		/// </summary>
		/// <param name="child">The child game object you are going to remove.</param>
		public void RemoveChild(GameObject child)
		{
			if (child.Parent != this || !Children.Contains(child))
			{
				throw new ManPowerException("You can't remove a game object that isn't a child of this object: " + child.ToString());
			}
			child.mParent = null;
			Children.Remove(child);
		}

		internal void _Update(TimeArgs Time)
		{
			// cancel recursing if this is disabled
			if (!Enabled)
				return;
			// call custom update function first
			Update(Time);

			List<GameObject> toBeUpdated = new List<GameObject>();
			// children preparation before update
			foreach (GameObject gameobject in Children)
			{
				if (gameobject.mParent != this)
				{
					throw new ManPowerException("There are some children that wasn't added by AddChild(): " + gameobject.ToString());
				}
				toBeUpdated.Add(gameobject);
			}

			// update its children
			foreach (GameObject gameobject in toBeUpdated)
			{
				// call the internal update method
				gameobject._Update(Time);
			}
		}

		internal void _PhysicsUpdate(double timeStep)
		{
			PhysicsUpdate(timeStep);
		}
		internal void _Collide(Collision collision)
		{
			OnCollide(collision);
		}

		public override string ToString()
		{
			return Name + ":" + Transform.LocalPosition;
		}

		/// <summary>
		/// Indicates whether if this game object is the root object or not.
		/// </summary>
		public bool IsRoot
		{
			get { return (mParent == null); }
		}

		internal void CleanUp()
		{
			// call custom stop event
			Stop();
			foreach (GameObject gameobject in Children)
			{
				gameobject.CleanUp();
			}
		}
	}
}
